#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <ltdl.h>

#include "imbus_internal.h"
#include "im_array.h"
#include "im_hash.h"
#include "im_string.h"
#include "im_module.h"

/************************** Declaration of internal functions ***************/
static void     __im_module_initialize      (IMPointer       module);
static void     __im_module_finalize        (IMPointer       module);
static void     __im_module_class_init      (IMPointer       klass);
static void     __im_module_class_finalize  (IMPointer       klass);

/****************** Declaration of object and class structure ************/
struct _IMModule
{
    IMInitiallyOwned parent_object;

    /* private */
    lt_dlhandle           handle;
    IMString             *path;

    IMModuleInitFunc      init;
    IMModuleFinalizeFunc  finalize;

    IMArray              *types;
};

struct _IMModuleClass
{
    IMInitiallyOwnedClass parent_class;

    IMHashTable  *opened_modules;
};

/************* Implementation of internal functions ***************/
static void
__im_module_initialize (IMPointer module)
{
    ((IMModule*)module)->handle    = 0;
    ((IMModule*)module)->path      = 0;
    ((IMModule*)module)->init      = 0;
    ((IMModule*)module)->finalize  = 0;
    ((IMModule*)module)->types     = 0;
}

static void
__im_module_finalize (IMPointer module)
{
    IMModule            *mp  = IM_MODULE (module);
    const IMModuleClass *mcp = IM_MODULE_GET_CLASS (module);

    _im_assert (mp != 0 && mcp != 0);

    if (mp->finalize)
        (*mp->finalize) (mp);

    /* Remove the module from module registry. */
    im_hash_table_remove (mcp->opened_modules, mp->init);

    if (mp->types) {
        IMSize i;
        for (i = 0; i < im_array_size (mp->types); ++i) {
            if (!im_type_deregister_class (im_array_index (mp->types, IMType, i))) {
                _im_warn_check_failed ("Type %s which belongs to module %s can't be deregistered!",
                        im_type_get_name (im_array_index (mp->types, IMType, i)),
                        (mp->path ? im_string_c_str (mp->path) : "unknown"));
            }
        }

        im_array_destroy (mp->types, TRUE);
    }

    if (mp->path)
        im_object_unref (mp->path);
}

static void
__im_module_class_init (IMPointer klass)
{
    IMObjectClass *cp  = (IMObjectClass *)klass;
    IMModuleClass *mcp = (IMModuleClass *)klass;

    /* IMModule objects don't support copy, serialize and deserialize */
    cp->copy        = 0;
    cp->serialize   = 0;
    cp->deserialize = 0;

    mcp->opened_modules = im_hash_table_new (im_direct_hash, 0);

    _im_assert (mcp->opened_modules != 0);
}

static void
__im_module_class_finalize (IMPointer klass)
{
    IMModuleClass *mcp = (IMModuleClass *)klass;

    im_hash_table_destroy (mcp->opened_modules);
}

/************* Implementation of public functions ***************/
void
__im_type_register_module ()
{
    static IMTypeInfo im_module_type_info =
    {
        sizeof (IMModuleClass),    /**< class_size >**/
        __im_module_class_init,    /**< class_init >**/
        __im_module_class_finalize,/**< class_finalize >**/

        sizeof (IMModule),         /**< instance_size >**/
        __im_module_initialize,    /**< instance_init >**/
        __im_module_finalize,      /**< instance_finalize >**/
    };

    __im_type_register_builtin_class (IM_TYPE_INITIALLY_OWNED,
                                      IM_TYPE_MODULE,
                                      "Module",
                                      &im_module_type_info,
                                      TRUE);
}

IMModule*
im_module_open (const IMChar *file_name)
{
    const IMModuleClass    *mcp = IM_MODULE_CLASS (im_type_class_from_type (IM_TYPE_MODULE));
    IMModule               *mp;
    lt_dlhandle             handle;
    IMModuleInitFunc        init;
    IMModuleFinalizeFunc    finalize;

    _im_return_val_if_fail (file_name != 0, 0);

    handle = lt_dlopenext (file_name);

    if (!handle)
        return 0;

    init     = (IMModuleInitFunc) lt_dlsym (handle, "im_module_init");
    finalize = (IMModuleFinalizeFunc) lt_dlsym (handle, "im_module_finalize");

    /* If im_module_init symbol is missing, then it's not a valid module. */
    if (!init) {
        lt_dlclose (handle);
        return 0;
    }

    mp = im_hash_table_lookup (mcp->opened_modules, (IMPointer)init);

    /* If the module has been opened, then just ref and return it. */
    if (mp) {
        lt_dlclose (handle);
        return im_object_ref (mp);
    }

    mp = im_object_new (IM_TYPE_MODULE);

    _im_assert (mp);

    mp->handle   = handle;
    mp->path     = im_object_ref (im_string_new (file_name));
    mp->init     = init;
    mp->finalize = finalize;

    /* Add this module to registry. */
    im_hash_table_insert (mcp->opened_modules, init, mp);

    /* Initialize the module. Destroy it if fail. */
    if (!(*init) (mp)) {
        /* Use im_type_instance_delete to make sure that
         * it will actually be destroyed.
         */
        im_type_instance_delete (mp);
        return 0;
    }

    return mp;
}

IMBool
im_module_close (IMModule *module)
{
    _im_return_val_if_fail (IM_IS_MODULE (module), FALSE);

    if (im_module_is_resident (module)) {
        _im_warn ("Module %s is resident, so that it can't be closed.",
                  im_string_c_str (module->path));
        return FALSE;
    }

    im_object_unref (module);
    return TRUE;
}

IMBool
im_module_valid (const IMModule *module)
{
    return IM_IS_MODULE (module) && module->handle != 0 && module->init != 0;
}

IMBool
im_module_make_resident (IMModule *module)
{
    _im_return_val_if_fail (IM_IS_MODULE (module), FALSE);
    return module->handle != 0 && lt_dlmakeresident (module->handle) == 0;
}

IMBool
im_module_is_resident (IMModule *module)
{
    _im_return_val_if_fail (IM_IS_MODULE (module), FALSE);
    return module->handle != 0 && lt_dlisresident (module->handle);
}

IMPointer
im_module_symbol (const IMModule *module, const IMChar *symbol_name)
{
    _im_return_val_if_fail (im_module_valid (module), 0);
    _im_return_val_if_fail (symbol_name && *symbol_name, 0);

    return lt_dlsym (module->handle, symbol_name);
}

const IMChar*
im_module_file_name (const IMModule *module)
{
    _im_return_val_if_fail (im_module_valid (module), 0);

    return module->path ? im_string_c_str (module->path) : 0;
}

IMString*
im_module_build_path (const IMChar *directory, const IMChar *module_name)
{
    /* TODO */
    return 0;
}

static IMInt
__im_type_compare (IMConstPointer a, IMConstPointer b)
{
    return ((IMInt)(*((const IMType*)a))) - ((IMInt)(*((const IMType*)b)));
}

IMType
im_module_register_type_class (IMModule         *module,
                               IMType            parent,
                               const IMChar     *class_name,
                               const IMTypeInfo *type_info,
                               IMBool            instantiatable)
{
    IMType type;
    IMSize idx;

    _im_return_val_if_fail (im_module_valid (module), 0);

    type = im_type_register_class (parent, class_name, type_info, instantiatable);

    _im_assert (type);

    if (!module->types)
        module->types = im_array_new (FALSE, TRUE, sizeof (IMType));

    idx = im_array_lower_bound (module->types, &type, __im_type_compare);

    /* The type is not registered. */
    if (idx >= im_array_size (module->types) ||
        im_array_index (module->types, IMType, idx) != type) {
        im_array_insert_val (module->types, idx, type);
    }

    return type;
}

IMBool
im_module_deregister_type_class (IMModule *module, IMType type)
{
    IMBool valid = FALSE;
    IMSize idx;

    _im_return_val_if_fail (im_module_valid (module), FALSE);

    if (module->types) {
        idx = im_array_lower_bound (module->types, &type, __im_type_compare);

        if (idx < im_array_size (module->types) &&
            im_array_index (module->types, IMType, idx) == type) {
            valid = TRUE;
        }
    }

    if (!valid) {
        _im_warn ("Type %s doesn't belong to module %s!",
                im_type_get_name (type),
                (module->path ? im_string_c_str (module->path) : "unknown"));
        return FALSE;
    }

    if (!im_type_deregister_class (type)) {
        _im_warn_check_failed ("Type %s which belongs to module %s can't be deregistered!",
                im_type_get_name (type),
                (module->path ? im_string_c_str (module->path) : "unknown"));
        return FALSE;
    }

    im_array_remove_index (module->types, idx);

    return TRUE;
}

/*
vi:ts=4:nowrap:ai:expandtab
*/
